from main.exceptions.exception import DeviceUnavailableException
from main.model.logic.interruption import Interruption
from threading import Condition
from main.model.logic.software.instructions import Instruction
from main.model.devices.cpu_controller import DumyCPUController


class CPU(object):

    def __init__(self, os_usher):
        """device_manager. context and kernel should be set before run"""
        self.__kernel         = None
        self.__device_manager = None
        self.__context        = None
        self.__os_usher       = os_usher
        self.__is_free        = True
        self.__stop           = False
        self.__waiting        = Condition()
        self.__controller     = DumyCPUController()

    def run(self):
        self.check_device_manager()
        self.wait_for_tick()
        while self.os.is_on_user_mode():
            self.run_instruction()
            self.wait_for_tick()
        pass

    def wait_for_tick(self):
        if(not self.__stop):
            with self.__waiting:
                self.__waiting.wait()

    def stop(self):
        self.__stop = True
        with self.__waiting:
            self.__waiting.notify()

    def run_instruction(self):
        """TODO create instructions handlers"""
        actual = self.memory.get(self.context.program_counter, self.context.pcb)
        self.device_manager.output.send(str(self.context.pcb.process_name) + " - " + str(actual))
        if(actual.itype() == Instruction.IO):
            self.trigger(Interruption.IO_REQUEST)
            self.set_free()
        elif(self.context.finished()):
            self.trigger(Interruption.KILL_PROCESS)
            self.set_free()
        else:
            self.controller.cycle()
        self.context.program_counter += 1
            
    def trigger(self, interruption_kind):
        interruption = Interruption(interruption_kind, self.context.pcb, cpu=self)
        self.kernel.add_interruption(interruption)

    def check_device_manager(self):
        if (not self.device_manager):
            raise DeviceUnavailableException("CPU")
    
    def set_free(self):
        self.__is_free = True
        
    def work_to_do(self):
        self.controller.reset()
        self.__is_free = False
        
    def tick(self, time_in_seconds):
        with self.__waiting:
            self.__waiting.notify()
    
    @property
    def controller(self): return self.__controller
    
    @controller.setter
    def controller(self, controller): self.__controller = controller
    
    @property
    def context(self): return self.__context
    
    @context.setter
    def context(self, context): self.__context = context
    
    @property
    def kernel(self): return self.__kernel
    
    @kernel.setter
    def kernel(self, kernel): self.__kernel = kernel
    
    @property 
    def device_manager(self): return self.__device_manager
    
    @device_manager.setter
    def device_manager(self, device_manager): self.__device_manager = device_manager
        
    @property
    def is_free(self): return self.__is_free
    
    @property
    def memory(self): return self.device_manager.memory
    
    @property
    def output(self): return self.device_manager.output
    
    @property 
    def os(self): return self.__os_usher
    
    @property 
    def clock(self): return self.device_manager.clock

